package pers.cz.io;

import pers.cz.log.LogUtils;

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * @program: JefConfig
 * @description: IO工具
 * @author: Cheng Zhi
 * @create: 2022-07-11 22:15
 **/
public class IOUtils {

    private static final int DEFAULT_BUFFER_SIZE = 4096;

    public static BufferedReader getReader(InputStream inputStream, String charset) {

        if (inputStream == null) {
            return null;
        }

        UnicodeReader isr = new UnicodeReader(inputStream, Charsets.forName(charset));
        return new BufferedReader(isr);
    }

    /**
     * 直接从文件中读取字符并存放到map中
     * @param fileInputStream
     * @return
     * @throws IOException
     */
    public static Map<String, String> loadProperties(FileInputStream fileInputStream) throws IOException {

        Map<String, String> map = new LinkedHashMap<>();
        try (FileChannel channel = fileInputStream.getChannel();) {
            //ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
            // 处理半包问题（读取文件较大，byteBuferr不能够一次性装载，需要分多次，此时将出现半包问题）,因此先读取文件大学，然后再申请内存
            int size = fileInputStream.available();
            ByteBuffer byteBuffer = ByteBuffer.allocateDirect(size); // 使用系统内存效率更高一些，零拷贝，但是内存分配可能比较慢

            while (true) {
                // 按照字节来读取
                int read = channel.read(byteBuffer);
                if (read < 0) {
                    break;
                }
                byteBuffer.flip();
                readLine(byteBuffer, map);
                byteBuffer.clear();
            }

            byteBuffer = null; // 帮忙jvm垃圾回收器通知调用freeMemory方法来释放系统内存
        }

        return map;
    }

    /**
     * 从缓存区中读取一行,存放到map中
     * @param byteBuffer
     */
    private static void readLine(ByteBuffer byteBuffer, Map<String, String> map) {
        // 是否为注释行
        boolean isCommendLine = false;
        // 是否为新行
        boolean isNewLine = false;
        // 是否时key后面的第一个有效字符
        boolean isAfterKey = false;
        Character splitKey = null;
        String key = "";
        String value = "";

        StringBuilder sb = new StringBuilder();
        while(byteBuffer.hasRemaining()) {
            char c = (char) byteBuffer.get();

            if (!key.equals("") && !value.equals("")) {
                map.put(key,value.trim());
                key = "";
                value = "";
                splitKey = null;
                isAfterKey = false;
            }

            // asdfghjk\nadfadfadf\nerer
            if (c == '\n' || c == '\r') {
                isNewLine = true;
                isCommendLine = false; // 如果遇到换行则重新判断是否为注释行
            } else {
                isNewLine = false;
            }
            // 判断是否找到value的有效字符
            if (splitKey != null && sb.length()>0) {
                isAfterKey = true;
            }
            // 如果第一个字符是空格，tab制表符则跳过
            if (c == ' ' || c == '\t') {
                //continue;
                /**
                 * 这里要判断是不是分割符后的第一个空格或者分割符后的连续空格，如 key    =     insert into test_tabel xxxxx
                 * 当遇到分割符后的第一个字符后，之后的空格应当保留
                 */
                if (!isAfterKey) {
                    continue;
                }
            }

            // 如果去除了空格之外是#或者！则表示注释行,注释行之后的内容不处理，直到遇到换行或者回车
            if (c == '#' || c == '!') {
                isCommendLine = true;
            }

            // 判断如果是新行，看是不是为空则不处理
            if (isNewLine) {
                // 如果是新行，且字符串长度为0,则不处理
                int length = sb.length();
                if (length == 0) {
                    continue;
                }
                isCommendLine = false;
                isNewLine = false;
                value = sb.toString();
                sb = new StringBuilder();
                continue;
            }
            // 如果已经找到key,并且找到valued的有效字符，则后面不需要进行任务判断，认为分隔符后面的有效字符之后的都是有用信息。原封不动拼接
            if (isAfterKey) {
                sb.append(c);
                continue;
            }
            if (!isCommendLine) {
                // 如果遇到 = ： 则认为要分割,前面的为key, 后面的为value, 如果前面已经有了=则后面的：不应该再继续拆分，反之亦然,当找到有效字符后，后面的字符，则不在检验各种格式
                if (c == '=' || c == ':') {
                    if (splitKey == null) {
                        splitKey = c;
                    }

                }

                if (splitKey != null && c == splitKey) {
                    key = sb.toString();
                    // 重置sb
                    sb = new StringBuilder();
                    continue;
                }
                sb.append(c);
            }

        }
    }

    /**
     * 关闭指定的对象，不会抛出异常
     *
     * @param input
     *            需要关闭的资源
     */
    public static void closeQuietly(Closeable input) {
        if (input != null) {
            try {
                input.close();
            } catch (IOException e) {
                LogUtils.error("错误", e);
            }
        }
    }

    /**
     * 将输入流保存为文件
     *
     * @param file
     * @param iss
     * @throws IOException
     */
    public static void saveAsFile(File file, InputStream... iss) throws IOException {
        ensureParentFolder(file);
        BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream(file));
        try {
            for (InputStream is : iss) {
                copy(is, os, false);
            }
        } finally {
            if (os != null) {
                os.flush();
                os.close();
            }
        }
    }

    /**
     * 获取一个输入字符缓冲流
     * @param file     目标文件
     * @param charset  编码方式， 如果传入null则使用默认模式编码
     * @param append   是否在文件末尾追加
     * @return
     */
    public BufferedWriter getWriter(File file, Charset charset, boolean append) throws FileNotFoundException {
        ensureParentFolder(file);

        FileOutputStream fileOutputStream = new FileOutputStream(file, append);

        if (charset == null) {
            charset = Charset.defaultCharset();
        }

        OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fileOutputStream, charset);
        return new BufferedWriter(outputStreamWriter);

    }
    /**
     * 检查/创建文件在所的文件夹。 如果该文件所在的文件夹已存在，什么也不做。 如果该文件所在的文件夹不存在，则创建
     *
     * @param file
     *            要检查的路径
     */
    public static void ensureParentFolder(File file) {
        File f = file.getParentFile();
        if (f != null && !f.exists()) {
            f.mkdirs();
        } else if (f != null && f.isFile()) {
            throw new RuntimeException(f.getAbsolutePath() + " is a exist file, can't create directory.");
        }
    }

    /**
     * 流之间拷贝
     *
     * @param in
     *            输入
     * @param out
     *            输出
     * @param closeOutStream
     *            关闭输出流? (输入流默认关闭)
     * @return
     * @throws IOException
     */
    public static long copy(InputStream in, OutputStream out, boolean closeOutStream) throws IOException {
        return copy(in, out, true, closeOutStream, new byte[DEFAULT_BUFFER_SIZE]);
    }

    private static long copy(InputStream in, OutputStream out, boolean inClose, boolean outClose, byte[] pBuffer) throws IOException {
        if (in == null)
            throw new NullPointerException();
        long total = 0;
        try {
            int res;
            while ((res = in.read(pBuffer)) != -1) {
                if (out != null) {
                    out.write(pBuffer, 0, res);
                }
                total += res;
            }
            if (out != null)
                out.flush();
        } finally {
            if (outClose)
                closeQuietly(out);
            if (inClose)
                closeQuietly(in);
        }
        return total;
    }
}
